use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
use syn::{punctuated::Punctuated, spanned::Spanned, *};

/// All type will be judged.
#[derive(PartialEq)]
enum ParamType {
    Value,
    OptionValue,
    OptionExpr,
    Expr,
    VecExpr,
    MutVecExpr,
    Environment,
    Other,
    OptionOther,
}

impl ParamType {
    pub fn to_token(&self) -> &str {
        match self {
            ParamType::Value => "env.eval(exprs.pop().unwrap())?",
            ParamType::Expr => "exprs.pop().unwrap()",
            ParamType::Other => "env.eval(exprs.pop().unwrap())?.try_into()?",
            ParamType::VecExpr => "exprs",
            ParamType::MutVecExpr => "&mut exprs",
            ParamType::Environment => "env.clone()",
            ParamType::OptionExpr => "exprs.pop()",
            ParamType::OptionValue => {
                "match exprs.pop() {
                Some(expr) => Some(env.eval(expr)?),
                None => None
            }"
            }
            ParamType::OptionOther => {
                "match exprs.pop() {
                Some(expr) => Some(env.eval(expr)?.try_into()?),
                None => None
            }"
            }
        }
    }

    pub fn is_eval_type(&self) -> bool {
        matches!(
            self,
            ParamType::Value | ParamType::Other | ParamType::OptionValue | ParamType::OptionOther
        )
    }
}

impl Default for ParamType {
    fn default() -> Self {
        Self::Other
    }
}

fn last_type_string(path: &Path) -> String {
    return path.segments.last().unwrap().ident.to_string();
}

fn get_angle_bracket_type(path: &Path) -> Option<Punctuated<GenericArgument, Token![,]>> {
    if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) =
        path.segments.last().unwrap().arguments.clone()
    {
        return Some(args);
    }
    None
}

fn get_angle_bracket_type_path(path: &Path) -> Option<Path> {
    if let Some(args) = get_angle_bracket_type(path) {
        if let GenericArgument::Type(Type::Path(TypePath { path, .. })) = args.last().unwrap() {
            return Some(path.clone());
        }
    }
    None
}

#[allow(unused)]
fn get_angle_bracket_type_trait_object(path: &Path) -> Option<TypeTraitObject> {
    if let Some(args) = get_angle_bracket_type(path) {
        if let GenericArgument::Type(Type::TraitObject(trait_object)) = args.last().unwrap() {
            return Some(trait_object.clone());
        }
    }
    None
}

#[allow(unused)]
fn get_trait_object_type_path(trait_object: &TypeTraitObject) -> Option<Path> {
    if let TypeParamBound::Trait(TraitBound { path, .. }) = trait_object.bounds.last().unwrap() {
        return Some(path.clone());
    }
    None
}

impl From<Type> for ParamType {
    /// Reference type only support `Environment`
    ///
    /// Other type will use `try_into`
    fn from(value: Type) -> Self {
        match value {
            Type::Path(TypePath { path, .. }) => {
                let last = last_type_string(&path);
                if last == "Value" {
                    return Self::Value;
                }
                if last == "Expr" {
                    return Self::Expr;
                }
                if last == "Vec" {
                    if let Some(path) = get_angle_bracket_type_path(&path) {
                        if last_type_string(&path) == "Expr" {
                            return Self::VecExpr;
                        }
                    }
                }
                if last == "Option" {
                    if let Some(path) = get_angle_bracket_type_path(&path) {
                        let last = last_type_string(&path);

                        return if last == "Value" {
                            Self::OptionValue
                        } else if last == "Expr" {
                            Self::OptionExpr
                        } else {
                            Self::OptionOther
                        };
                    }
                }
                if last == "Rc" {
                    if let Some(path) = get_angle_bracket_type_path(&path) {
                        let last = last_type_string(&path);

                        if last == "Env" {
                            return Self::Environment;
                        }
                    }
                }
            }
            Type::Reference(TypeReference {
                elem, mutability, ..
            }) => {
                if mutability.is_none() {
                    return ParamType::Other;
                }
                if let Type::Path(TypePath { path, .. }) = *elem {
                    let last = last_type_string(&path);
                    if last == "Vec" {
                        if let Some(path) = get_angle_bracket_type_path(&path) {
                            let last = last_type_string(&path);

                            if last == "Expr" {
                                return ParamType::MutVecExpr;
                            }
                        }
                    }
                }
            }
            _ => {}
        }
        Self::Other
    }
}

#[derive(PartialEq)]
enum TypeState {
    Normal = 1 << 0,
    Option = 1 << 1,
    VecExpr = 1 << 2,
    Environment = 1 << 3,
}

impl From<TypeState> for u8 {
    fn from(val: TypeState) -> Self {
        val as u8
    }
}

impl TypeState {
    pub fn has_option(state: u8) -> bool {
        (state & Self::Option as u8) != 0
    }

    pub fn has_env(state: u8) -> bool {
        (state & Self::Environment as u8) != 0
    }

    pub fn has_vec_expr(state: u8) -> bool {
        (state & Self::VecExpr as u8) != 0
    }
}

struct ParamatersEval {
    pub most_len: usize,
    pub least_len: usize,
    pub params_str: String,
    pub most_limit: bool,
}

impl ParamatersEval {
    pub fn new(most_len: usize) -> Self {
        Self {
            most_len,
            least_len: 0,
            params_str: String::new(),
            most_limit: true,
        }
    }

    pub fn params_length_token<T: ToString>(self, fn_name: T) -> TokenStream2 {
        let Self {
            most_len,
            least_len,
            params_str: _,
            most_limit,
        } = self;
        let fn_name: String = fn_name.to_string();
        if most_limit {
            if most_len == least_len {
                return quote! {
                    if exprs.len() != #least_len {
                        return Err(RuntimeError::TooFewArguments { func: #fn_name, needs: #least_len });
                    }
                };
            }
            return quote! {
                if exprs.len() < #least_len {
                    return Err(RuntimeError::TooFewArguments { func: #fn_name, needs: #least_len });
                } else if exprs.len() > #most_len {
                    return Err(RuntimeError::Custom(format!("The maximum parameter exceeded is the {} limit (builtin {})", #most_len, #fn_name)));
                }
            };
        }
        quote! {
            if exprs.len() < #least_len {
                return Err(RuntimeError::TooFewArguments { func: #fn_name, needs: #least_len });
            }
        }
    }
}

pub struct BuiltinParser {
    name: Option<LitStr>,
    attrs: Vec<Attribute>,
    vis: Visibility,
    sig: Signature,
    block: Box<Block>,
}

impl BuiltinParser {
    pub fn new(name: Option<LitStr>, item_fn: ItemFn) -> Self {
        let ItemFn {
            attrs,
            vis,
            sig,
            block,
        } = item_fn;
        Self {
            name,
            attrs,
            vis,
            sig,
            block,
        }
    }

    fn fn_name(&self) -> Ident {
        self.sig.ident.clone()
    }
    /// Build struct token stream
    ///
    /// Name from fn name.
    fn get_struct_token(&self) -> TokenStream2 {
        // get method doc
        let doc_attrs: Vec<Attribute> = self
            .attrs
            .iter()
            .filter(|attr| attr.path().is_ident("doc"))
            .cloned()
            .collect();
        let vis = &self.vis;
        let fn_name = self.fn_name();
        match &self.name {
            Some(name) => {
                quote! {
                    #(#doc_attrs)*
                    #[allow(non_camel_case_types, missing_docs)]
                    #vis struct #fn_name {
                        #[allow(dead_code)]
                        pub name: String,
                    }
                    impl Default for #fn_name {
                        #[allow(dead_code)]
                        fn default() -> Self {
                            Self { name: (#name).to_string() }
                        }
                    }
                }
            }
            None => {
                quote! {
                    #(#doc_attrs)*
                    #[allow(non_camel_case_types, missing_docs)]
                    #[derive(Default)]
                    #vis struct #fn_name;
                }
            }
        }
    }

    fn get_fn_paramaters(&self) -> Result<ParamatersEval> {
        let params = self.sig.inputs.clone();
        let mut state = TypeState::Normal as u8;

        let mut param_eval = ParamatersEval::new(self.sig.inputs.len());
        for (idx, arg) in params.iter().enumerate() {
            // if not the first paramater, need add char `,`
            if idx != 0 {
                param_eval.params_str.push(',');
            }
            // get args type
            if let FnArg::Typed(PatType { ty, .. }) = arg {
                let param_type = ParamType::from(*ty.clone());
                // Vec<Expr> can only set at the lastest of params
                if TypeState::has_vec_expr(state) {
                    if param_type == ParamType::Environment {
                        if TypeState::has_env(state) {
                            return Err(Error::new(
                                arg.span(),
                                "The Environment only can have one at the same time",
                            ));
                        }
                        param_eval.params_str.push_str(param_type.to_token());
                        param_eval.most_len -= 1;
                        state |= TypeState::Environment as u8;
                        continue;
                    }
                    return Err(Error::new(
                        arg.span(),
                        "Vec<Expr> can only set at the lastest of params",
                    ));
                }

                if param_type.is_eval_type() && TypeState::has_env(state) {
                    return Err(Error::new(
                        arg.span(),
                        "Evaluated value cannot be placed after `&mut Environment`",
                    ));
                }

                if TypeState::has_option(state) {
                    match param_type {
                        ParamType::Environment => {
                            if TypeState::has_env(state) {
                                return Err(Error::new(
                                    arg.span(),
                                    "The Environment only can have one at the same time",
                                ));
                            }
                            param_eval.most_len -= 1;
                            state |= TypeState::Environment as u8;
                        }
                        ParamType::VecExpr | ParamType::MutVecExpr => {
                            param_eval.most_limit = false;
                            state |= TypeState::VecExpr as u8;
                        },
                        ParamType::Value | ParamType::Other | ParamType::Expr => {
                            return Err(Error::new(
                                arg.span(),
                                "After Option<T> cannot set any paramater without Option except Environment",
                            ))
                        }
                        _ => {}
                    }
                } else {
                    match param_type {
                        ParamType::Value | ParamType::Expr | ParamType::Other => {
                            param_eval.least_len += 1;
                        }
                        ParamType::OptionValue | ParamType::OptionExpr | ParamType::OptionOther => {
                            state |= TypeState::Option as u8;
                        }
                        ParamType::VecExpr | ParamType::MutVecExpr => {
                            param_eval.most_limit = false;
                            state |= TypeState::VecExpr as u8;
                        }
                        ParamType::Environment => {
                            if TypeState::has_env(state) {
                                return Err(Error::new(
                                    arg.span(),
                                    "The Environment only can have one at the same time",
                                ));
                            }
                            param_eval.most_len -= 1;
                            state |= TypeState::Environment as u8;
                        }
                    }
                }
                param_eval.params_str.push_str(param_type.to_token());
            } else {
                return Err(Error::new(
                    arg.span(),
                    "`builtin_method` don't support this method.",
                ));
            }
        }
        Ok(param_eval)
    }

    pub fn build(&self) -> Result<TokenStream2> {
        let builtin_struct = self.get_struct_token();
        let fn_name = self.fn_name();

        let params_eval = self.get_fn_paramaters()?;
        let params_input: TokenStream2 = params_eval.params_str.clone().parse()?;

        let params_length_judge = params_eval.params_length_token(fn_name.clone());

        let Self {
            attrs, sig, block, ..
        } = self;

        let params = sig.inputs.clone();
        let out_type = sig.output.clone();

        Ok(quote! {
            #builtin_struct
            impl #fn_name {
                #(#attrs)*
                pub fn eval(#params) #out_type #block
                pub fn gen(env: Rc<Env>, exprs: Vec<Expr>) -> Result<Value, dj::runtime::Error> {
                    use dj::runtime::Error as RuntimeError;
                    use std::collections::VecDeque;
                    #params_length_judge
                    let mut exprs = exprs;
                    Self::eval(#params_input)
                }
            }
        })
    }
}

impl ToTokens for BuiltinParser {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        tokens.extend(match self.build() {
            Ok(build) => build,
            Err(err) => err.to_compile_error(),
        })
    }
}
